# Python 闭包函数
# 闭包函数是指，一个函数内部定义了另一个函数，并且这个函数使用了外部函数的变量。
# 闭包函数的作用是，可以隐藏外部函数的变量，保护内部函数的变量不被外部函数修改。


# 定义一个函数
def outer():          # 外部函数
    n = 2
    def inner(x):     # 内部函数
        return x ** n
    return inner      # 返回内部函数, 注意这里返回的是内部函数的引用, 而不是内部函数的执行结果, 所以这里不需要括号
# 调用函数
f = outer()
print("闭包函数type:", type(f))
print("闭包函数结果:", f(3))    # 9


# 正常的闭包案例1：
# 外部函数返回内部函数时，如果内部函数使用了外部函数的变量，那么外部函数的变量也会被内部函数引用，
# 因此，外部函数的变量值会发生变化，这就是闭包函数的作用。
def outer():
    n = 2
    def inner(x):
        return x ** n    # x的平方，这里的n实际上是外部函数的n
    return inner
# 定义一个变量
n = 3
f = outer()
print("奇怪案例1结果:", f(4))    # 16
# __closure__ 属性可以查看闭包函数的内部函数引用了哪些外部函数的变量
# 输出：# (<cell at 0x0000024AF4F0A5F0: int object at 0x00007FFBDE8823C8>,)
# 表示该闭包函数内部函数引用了外部函数的变量是int类型对象，值为2。
print(f.__closure__)    # (<cell at 0x0000024AF4F0A5F0: int object at 0x00007FFBDE8823C8>,)


# 奇怪的闭包案例2：
# 这种情况是不属于闭包函数的环境变量，等同于方法调用使用了全局变量
m = 2
def outer():
    def inner(x):
        return x ** m   # 这里实际上使用的是全局变量m，故而m = 3, 所以x的三次方为64
    return inner
# 定义一个变量
m = 3
f = outer()
print("奇怪案例2结果:", f(4))    # 64
# __closure__ 属性为None，表示该闭包函数没有引用外部函数的变量
print(f.__closure__)    # None


# 定义闭包函数修改外部函数变量值
# 内部函数是不能直接修改外部函数的变量值的，但是可以通过nonlocal关键字声明内部函数使用了外部函数的变量。
def create_counter():       # 外部函数
    count = 5
    def increment(n):       # 内部函数
        nonlocal count      # nonlocal 声明内部函数使用了外部函数的变量
        count += n
        return count
    return increment        # 返回内部函数, 注意这里返回的是内部函数的引用, 而不是内部函数的执行结果, 所以这里不需要括号
# 调用闭包函数
counter = create_counter()
print("修改后结果:", counter(8))
# 使用 __closure__ 同样可以看到内部函数引用了外部函数的变量
print(counter.__closure__) # (<cell at 0x0000024AF4F0A5F0: int object at 0x00007FFBDE8823C8>,)


# 奇葩的闭包案例3：
def outer():
    j = 2                         # 外部函数的变量
    l = []                        # 定义一个列表用于保存内部函数
    for i in range(1, 3):         # 使用for循环生成2个内部函数
        def inner():
            return i ** j         # 内部函数的返回值是i的j次方, 这里的j实际上是外部函数的j
        l.append(inner)           # 将内部函数添加到列表中
    return l                      # 返回内部函数列表
f1, f2 = outer()
print("奇葩案例3结果1:", f1())    # 4
print("奇葩案例3结果2:", f2())    # 4
# 输出：
# f1函数的环境属性值: 2 2
# f2函数的环境属性值: 2 2
# 可以看到以上输出结果都是4，这是为什么呢？通过 __closure__ 查看内部函数都引用了外部函数两个变量,分别是j和i。
print(f1.__closure__)    # (<cell at 0x000001DF080CAE30: int object at 0x00007FFBDE9C23C8>, <cell at 0x000001DF080CABC0: int object at 0x00007FFBDE9C23C8>)
print(f2.__closure__)    # (<cell at 0x000001DF080CAE30: int object at 0x00007FFBDE9C23C8>, <cell at 0x000001DF080CABC0: int object at 0x00007FFBDE9C23C8>)
# 但为什么内部函数的返回值都是4呢？因为内部函数的i和j都是2，所以i的j次方为4。
# 可以通过 __closure__.[index].cell_contents 属性查看内部函数引用外部函数的变量值
# 可以看到 j和i的cell_contents属性值都是2。这就很奇怪，一开始i的值不应该是1吗？
# 这是因为Python的函数只有在执行的时候才会确定参数值，而使用循环生成函数的时候，引用外部函数i的变量值是循环完成之后的值，
# 所以i的值是2，而j的值是2，所以i的j次方为4。
print("f1函数的环境属性值:", f1.__closure__[0].cell_contents, f1.__closure__[1].cell_contents)
print("f2函数的环境属性值:", f2.__closure__[1].cell_contents, f2.__closure__[1].cell_contents)

# 很显然上面的结果不是我们想要的，所以我们需要修改内部函数的定义，使得内部函数的i和j的值是不同的。
# 我们可以修改内部函数的定义，使得i和j的值是不同的，这样就可以得到不同的结果。
def outer():
    j = 2                         # 外部函数的变量
    l = []                        # 定义一个列表用于保存内部函数
    for i in range(1, 3):         # 使用for循环生成2个内部函数
        def inner(x):             # 内部函数定义时接收一个参数x
            return x ** j         # 内部函数的返回值是x的j次方, 这里的x实际上是循环变量i, j引用的是外部函数的j
        l.append(inner(i))        # 将内部函数的执行结果添加到列表中(这里实际上已经执行了内部函数,并将参数i传入了内部函数)
    return l                      # 返回内部函数执行结果列表
f1, f2 = outer()
# 注意：这里的f1和f2是执行结果，而不是函数引用，所以不需要使用括号调用。
print("修改后的结果1:", f1, type(f1))    # 1
print("修改后的结果2:", f2, type(f2))    # 4
# 因为f1和f2是执行结果，而不是函数引用，所以不能使用 __closure__ 属性，实际上已经不存在闭包函数的概念了
# 直接就是函数的调用， 将执行结果保存到了列表中
# print(f1.__closure__)
# print(f2.__closure__)
